package ar.edu.itba.it.cg.grupo01.impl.shape;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.vecmath.Matrix4f;
import javax.vecmath.Point2f;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import ar.edu.itba.it.cg.grupo01.api.Intersection;
import ar.edu.itba.it.cg.grupo01.api.Ray;
import ar.edu.itba.it.cg.grupo01.api.shape.Shape;
import ar.edu.itba.it.cg.grupo01.impl.IntersectionFactory;
import ar.edu.itba.it.cg.grupo01.impl.Precision;

public class Sphere extends AbstractShape {

	private Point3f center;
	private float radius;
	private Vector3f up;

	public Sphere(IntersectionFactory intersectionFactory) {
		this(new Point3f(0.0f, 0.0f, 0.0f), 1.0f,
				new Vector3f(0.0f, 1.0f, 0.0f), intersectionFactory);
	}

	public void setCenter(Point3f center) {
		this.center = center;
	}

	public void setRadius(float radius) {
		this.radius = radius;
	}

	public void setUp(Vector3f up) {
		this.up = up;
		this.up.normalize();
	}

	public Sphere(final Point3f center, final float radius,
			IntersectionFactory intersectionFactory) {
		this(center, radius, intersectionFactory, null);
	}

	public Sphere(final Point3f center, final float radius, final Vector3f up,
			IntersectionFactory intersectionFactory) {
		this(center, radius, up, intersectionFactory, null);
	}

	public Sphere(final Point3f center, final float radius,
			IntersectionFactory intersectionFactory, Color color) {
		this(center, radius, new Vector3f(0.0f, 1.0f, 0.0f),
				intersectionFactory, color);
	}

	public Sphere(final Point3f center, final float radius, final Vector3f up,
			IntersectionFactory intersectionFactory, Color color) {
		super(intersectionFactory, color);
		checkNotNull(center, "Sphere center should not be null");
		checkArgument(radius > 0.0, "Radius should be greater than 0.0");
		this.center = center;
		this.radius = radius;
		this.up = up;
	}

	@Override
	public Intersection<? extends Shape> intersects(Ray ray) {
		Vector3f dst = new Vector3f(ray.getOrigin());
		dst.sub(center);
		float b = 2 * (ray.getDirection().dot(dst));
		float c = dst.lengthSquared() - (radius * radius);

		float disc = b * b - 4 * c;

		if (disc < 0.0f)
			return intersectionFactory.emptyIntersection();

		float distSqrt = (float) Math.sqrt(disc);
		float q;
		if (b < 0) {
			q = (-b - distSqrt) / 2.0f;
		} else {
			q = (-b + distSqrt) / 2.0f;
		}

		float t0 = q;
		float t1 = c / q;

		if (t0 > t1) {
			float temp = t0;
			t0 = t1;
			t1 = temp;
		}

		if (t1 < 0)
			return intersectionFactory.emptyIntersection();

		Point3f intersectionPoint = new Point3f();
		Vector3f modifiedRay = ray.getDirection();
		Intersection.Type type;

		if (t0 < 0) {
			modifiedRay.scale(t1);
			type = Intersection.Type.IN;
		} else {
			modifiedRay.scale(t0);
			type = Intersection.Type.OUT;
		}

		intersectionPoint.add(ray.getOrigin(), modifiedRay);
		return intersectionFactory.createIntersection(intersectionPoint, this,
				type);
	}

	/*
	 * @Override public Intersection<? extends Shape> intersects(final Ray ray)
	 * { Vector3f dst = new Vector3f(ray.getOrigin()); dst.sub(center); float b
	 * = 2 * (ray.getDirection().dot(dst)); float c = dst.lengthSquared() -
	 * (radius * radius); float d = b * b - 4 * c; if (d < 0) { return
	 * intersectionFactory.emptyIntersection(); }
	 * 
	 * float x1 = (-b - Math.sqrt(d)) / 2; float x2 = (-b + Math.sqrt(d)) / 2;
	 * 
	 * if (x2 > 0) { Point3f intersectionPoint; Intersection.Type type; if (x1 <
	 * 0) { intersectionPoint = buildIntersectionPoint(x1, ray); type =
	 * Intersection.Type.IN; } else { intersectionPoint =
	 * buildIntersectionPoint(x2, ray); type = Intersection.Type.OUT;
	 * 
	 * }
	 * 
	 * return intersectionFactory.createIntersection(intersectionPoint, this,
	 * type); }
	 * 
	 * return intersectionFactory.emptyIntersection();
	 * 
	 * }
	 */
	public Point3f getCenter() {
		return (Point3f) center.clone();
	}

	public float getRadius() {
		return radius;
	}

	private Point3f buildIntersectionPoint(Float result, Ray ray) {

		Point3f base = ray.getOrigin();
		Vector3f direction = ray.getDirection();
		Point3f newDirection = new Point3f(direction.x * result, direction.y
				* result, direction.z * result);
		base.add(newDirection);
		return base;

	}

	@Deprecated
	public void scalex(float factor) {
		scaleu(factor);
	}

	@Deprecated
	public void scaley(float factor) {
		scaleu(factor);
	}

	@Deprecated
	public void scalez(float factor) {
		scaleu(factor);
	};

	// @Deprecated
	// public Shape applyTransformation() {
	//
	// Point3f edge = new Point3f(up);
	// edge.scaleAdd(radius, center);
	//
	// Point3f newCenter = new Point3f(center);
	//
	// transformations.transform(newCenter);
	// transformations.transform(edge);
	//
	// Vector3f newUp = new Vector3f(edge);
	// newUp.sub(newCenter);
	//
	// return new Sphere(newCenter, Math.abs(newCenter.distance(edge)), newUp,
	// intersectionFactory);
	// }

	@Override
	public boolean containedInAABB(AABB box) {

		Point3f vmin = box.getVmin();
		Point3f vmax = box.getVmax();

		float r2 = (float) Math.pow(radius, 2);

		float dmin = 0;

		dmin += containedInAABB(center.x, vmin.x, vmax.x);
		dmin += containedInAABB(center.y, vmin.y, vmax.y);
		dmin += containedInAABB(center.z, vmin.z, vmax.z);

		return (dmin <= r2) ? true : false;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((center == null) ? 0 : center.hashCode());
		long temp;
		temp = Float.floatToIntBits(radius);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		result = prime * result + ((up == null) ? 0 : up.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {

		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Sphere other = (Sphere) obj;

		if (other.center.epsilonEquals(this.center, Precision.EPSILON)
				&& Precision.epsilonEquals(this.radius, other.radius,
						Precision.EPSILON)
				&& other.up.epsilonEquals(this.up, Precision.EPSILON))
			return true;

		return false;
	}

	private float containedInAABB(float center, float vmin, float vmax) {
		float dmin = 0;

		if (center < vmin) {
			dmin += Math.pow(center - vmin, 2);
		} else if (center > vmax) {
			dmin += Math.pow(center - vmax, 2);
		}

		return dmin;

	}

	@Override
	public Point3f getMaxXYZ() {
		return new Point3f(center.x + radius, center.y + radius, center.z
				+ radius);
	}

	@Override
	public Point3f getMinXYZ() {
		return new Point3f(center.x - radius, center.y - radius, center.z
				- radius);
	}

	@Override
	public Vector3f getNormal(Point3f point) {
		Point3f normal = (Point3f) point.clone();
		normal.sub(center);
		Vector3f result = new Vector3f(normal);
		result.normalize();
		return result;
	}

	@Override
	public Shape applyTransformation(Matrix4f tmat) {

		Point3f newCenter = new Point3f(center);
		tmat.transform(newCenter);

		Point3f radiusPoint = new Point3f(up);
		radiusPoint.scaleAdd(radius, center);

		tmat.transform(radiusPoint);

		Vector3f newUpVector = new Vector3f(radiusPoint);
		newUpVector.sub(center);

		float newRadius = newUpVector.length();

		return new Sphere(newCenter, newRadius, newUpVector,
				intersectionFactory);
	}

	@Override
	public Shape applyTransformation() {

		Point3f newCenter = new Point3f(center);
		transformations.transform(newCenter);

		Point3f radiusPoint = new Point3f(up);
		radiusPoint.scaleAdd(radius, center);

		transformations.transform(radiusPoint);

		Vector3f newUpVector = new Vector3f(radiusPoint);
		newUpVector.sub(newCenter);

		float newRadius = newUpVector.length();

		Shape newShape = new Sphere(newCenter, newRadius, newUpVector,
				intersectionFactory);
		newShape.setShader(this.getShader());

		return newShape;
	}

	@Override
	public Shape getTransformedShape() {
		return new Sphere(center, radius, up, intersectionFactory);
	}

	@Override
	public Collection<Point3f> getVertices() {
		List<Point3f> points = new ArrayList<Point3f>();
		points.add(center);
		Point3f radiusPoint = new Point3f(up);

		// second point in upVector direction
		radiusPoint.scaleAdd(radius, center);
		points.add(radiusPoint);

		return points;
	}

	@Override
	public Point2f getUVCoordinates(Point3f point) {

		Point3f point2 = new Point3f(point);
		point2.sub(this.center);

		float u = (float) (Math.asin(this.getNormal(point).x) / Math.PI + 0.5);
		float v = (float) (Math.asin(this.getNormal(point).y) / Math.PI + 0.5);

		// TODO: Pasar esto al constructor!!!
		Point3f reference = new Point3f(center);
		Vector3f up = new Vector3f(this.up);
		up.normalize();
		up.scale(this.radius);
		reference.add(up);

		float ur = (float) (Math.asin(this.getNormal(reference).x) / Math.PI + 0.5);
		float vr = (float) (Math.asin(this.getNormal(reference).y) / Math.PI + 0.5);

		u = (u + ur) - (float) Math.floor(u + ur);
		v = (v + vr) - (float) Math.floor(v + vr);

		// float v= Math.acos(point.z/this.radius) / Math.PI;
		// float u= Math.acos ( point.x/(this.radius)* Math.sin (Math.PI*v) ) /
		// 2*Math.PI;
		// float u = point.x
		// / Math.sqrt(point.x * point.x + point.y * point.y + point.z
		// * point.z);
		// float v = point.y
		// / Math.sqrt(point.x * point.x + point.y * point.y + point.z
		// * point.z);
		// if(u<0){
		// u=1-u;
		// }
		// if(v<0){
		// v=1-v;
		// }

		return new Point2f(u, v);
	}

	@Override
	public Point3f getUVWCoordinates(Point3f point) {
		Point3f uvw = (Point3f) point.clone();
		uvw.sub(center);
		return uvw;
	}

}